package client;

import client.net.handler.ClientPacketHandler;
import client.net.handler.GameEndHandler;
import client.net.handler.GameInviteHandler;
import client.net.handler.GameStartHandler;
import client.net.handler.ObserveGameEndHandler;
import client.net.handler.ObserveGameUpdateHandler;
import client.net.handler.PingHandler;
import java.nio.ByteBuffer;
import java.util.EnumMap;
import java.util.logging.Level;
import java.util.logging.Logger;
import shared.net.protocol.ServerHeader;

/**
 * This class processes incoming packets.
 *
 * @author Vincent Tsuei
 * @author Nathan Poslusny
 * @version 2012-11-18
 */
public class ClientPacketProcessor implements Runnable {

    //This mapping allows us to get the right handler for a packet by simply querying the header of the packet.
    private static final EnumMap<ServerHeader.Send, ClientPacketHandler> HandlerMap = new EnumMap<ServerHeader.Send, ClientPacketHandler>(ServerHeader.Send.class);
    static {
        HandlerMap.put(ServerHeader.Send.PING, new PingHandler());
        HandlerMap.put(ServerHeader.Send.GAME_START_NOTIFY, new GameStartHandler());
        HandlerMap.put(ServerHeader.Send.GAME_INVITE_NOTIFY, new GameInviteHandler());
        HandlerMap.put(ServerHeader.Send.GAME_END_NOTIFY, new GameEndHandler());
        HandlerMap.put(ServerHeader.Send.OBSERVE_GAME_UPDATE, new ObserveGameUpdateHandler());
        HandlerMap.put(ServerHeader.Send.OBSERVE_GAME_END_NOTIFY, new ObserveGameEndHandler());
    }
    
    /* The client associated with this packet processor */
    private final Client client;
    /* Whether this processor is shutting down */
    private boolean shutdown = false;

    public ClientPacketProcessor(Client client) {
        this.client = client;
    }

    /**
     * Processing loop.
     */
    @Override
    public void run() {
        while (!shutdown) {
            ByteBuffer packet = null;
            try {
                packet = client.getNextInputPacket();
            } catch (InterruptedException ex) {
                //Should only happen when we are shutting down.
                if (!shutdown) {
                    Logger.getLogger(ClientIOStore.class.getName()).log(Level.SEVERE, null, ex);
                }
                return;
            }

            // Get the packet type. 
            // Then, check if this is a response the client is expecting.
            // If yes, pass it to the client directly.
            // If no, get the handler and pass the packet to the handler.
            byte header = packet.get();
            ServerHeader.Send sendType = ServerHeader.Send.getHeaderFromByte(header);
            if (sendType == null) {
                Logger.getLogger(ClientIOStore.class.getName()).log(Level.SEVERE, "Unrecognized header received from {0} - {1}",
                        new Object[]{client.getServerConnection().getAddressAndPortString(), header});
                return;
            }
            
            Logger.getLogger(ClientIOStore.class.getName()).log(Level.FINE, "Packet Received {0}", sendType.name());
            if (client.isWaitingForResponse() && client.getWaitResponseHeader() == sendType) {
                try {
                    client.notifyResponse(packet);
                } catch (InterruptedException ex) {
                    throw new RuntimeException(ex); //Nothing we can do.
                }
            } else {
                ClientPacketHandler packetProcessor = HandlerMap.get(sendType);
                if (packetProcessor == null) {
                    Logger.getLogger(ClientIOStore.class.getName()).log(Level.SEVERE, "Unhandled SEND packet type - {0}",
                            new Object[]{sendType.name()});
                    return;
                }
                packetProcessor.processPacket(client, packet);
            }
        }
    }

    /**
     * Set the shutdown flag.
     */
    public void shutdown() {
        shutdown = true;
    }
}
